home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Pascal / OffscreenToys / OffscreenToys 1.3 (boost) / OffscreenToysBoost.p < prev    next >
Encoding:
Text File  |  1995-01-11  |  22.9 KB  |  714 lines  |  [TEXT/PJMM]

  1. {--------- OFFSCREEN TOYS 1.3 (boosted version) ---------}
  2. {by Ingemar Ragnemalm 1994-1995}
  3.  
  4. {An attempt to make a simple, small, stand-alone, compatible offscreen animation demo.}
  5. {I made this since many people want to learn the internals of animation, which packages}
  6. {like SAT (Sprite Animation Toolkit) and aren't good for, even if I had included source.}
  7. {Source code examples should be small!}
  8.  
  9. {Some good points with Offscreen Toys:}
  10. {- It's free, with full source code.}
  11. {- It's complete; no other libraries needed.}
  12. {- It's small, both the source and the compiled binary.}
  13. {- It is a real Mac application with decent event processing.}
  14. {- It works both with and without Color QuickDraw!}
  15. {- Demonstrates the use of fixed-point numbers for speed and positions.}
  16.  
  17. {Some bad points:}
  18. {- It's not designed for re-using for complicated animation projects (which SAT is). The list}
  19. {of sprites is an array with no possibility to handle different kinds of sprites. (That's up to}
  20. {you to add if you want to build upon this.)}
  21. {- It doesn't give the maximum speed possible. You can make QuickDraw draw faster, and you}
  22. {can draw directly to screen. (SPEED CLOSE TO OPTIMAL QD FOR BOOSTED VERSION!)}
  23. {- It is neither MultiFinder-aware nor AppleEvent-aware.}
  24. {- The sprite movement isn't as trivial as it could have been, especially the collision handling}
  25. {but also the fixed-point positions, but hey, I need some fun too! Do we want a version that is}
  26. {strictly integer-based?}
  27. {- A few things that should be const'ed are hard-coded, i.e. the size of the icon (32x32) and}
  28. {the number of fixed-point "binary decimals" (4).}
  29.  
  30. {Offscreen Toys 1.1 adds better collision handling, making the marbles bounce in a more}
  31. {realistic way.}
  32. {Offscreen Toys 1.2 fixes (?) the bug that sometimes caused a marble to dosappear for a}
  33. {few seconds.}
  34. {The "boosted" version uses CopyBits from an offscreen to draw the sprites rather than}
  35. {PlotCIcon. This should speed things up quite a bit.}
  36. {Version 1.3 takes out all reuseable code to a separate file. I hope that will make each part}
  37. {easier to understand and makes the reuseable code easily used from other programs.}
  38.  
  39. program OffscreenToysPlus;
  40.     uses
  41. {$IFC UNDEFINED THINK_PASCAL}
  42.         Types, QuickDraw, Events, Windows, Dialogs, Fonts, DiskInit, TextEdit, Traps, Desk, Memory,{}
  43.         SegLoad, Scrap, ToolUtils, OSEvents, OSUtils, Menus, Resources, Packages, {}
  44. {$ENDC}
  45.         QDOffscreen, Sound, OffScreenToysUtils;
  46.  
  47. { --- PART 1: Variables and constants: -----------------------------------------}
  48.  
  49.     const
  50.         kAppleID = 128;
  51.         kFileID = 129;
  52.         kMBarHeight = 20;    { We assume 20 pixels menu bar for window sizing and dragging.}
  53.  
  54.         kWindID = 128;            { Window resource ID }
  55.         kAboutAlertID = 128;    { Alert resource ID }
  56.  
  57.         kSpriteNumber = 5;        { Number of moving objects }
  58.     var
  59.         gWhoa: Boolean;            {True when we want to quit}
  60.         gCollisionFlag: Boolean;    {Collisions or not?}
  61.  
  62. {Menu handles}
  63.         appleMenu, fileMenu: MenuHandle;
  64.  
  65. {The window we'll be using}
  66.         gWind: WindowPtr;
  67.  
  68. {Our two offscreens:}
  69.         offScreen, backScreen: GrafPtr;
  70.  
  71. {An offscreen that the cicn is preloaded to}
  72.         gCicn: GrafPtr;
  73.  
  74. {Sprite information. In real games, I prefer making a linked list of records, like I do in}
  75. {SAT, and a lot more information for each, but here we want it *simple*.}
  76. {- position: The positions in local coordinates for the window}
  77. {- fixedPos: 16 times position, which gives us fixed-point numbers}
  78. {- speed: Speed vectors that is added to fixedPos for every frame}
  79. {- r: Rectangles used in drawing, for remembering what part of the screen to update}
  80.         position, fixedPos: array[1..kSpriteNumber] of Point;
  81.         speed: array[1..kSpriteNumber] of Point;
  82.         r: array[1..kSpriteNumber] of Rect;
  83.  
  84. {A longint that shows how big the "bowl" is (assumes circular, not oval, bowl!):}
  85.         gBowlSize: Longint;
  86.  
  87. { --- PART 2: Various general, reuseable routines, mostly glue: ---------------------}
  88. {All except Rand moved to OffscreenToysUtils!}
  89.  
  90.     function Rand (range: integer): integer;
  91.     begin
  92.         {Rand := abs(Random mod range) - doesn't work with CodeWarrior}
  93.         Rand := abs(Integer(Random) mod range);
  94.     end;
  95.  
  96. { --- PART 3: Application specific routines: ---------------------------------}
  97.  
  98. {mouse clicks, keydowns, background tasks and update events: This is where all}
  99. {the action is. :-) I include some empty procedures for you to fill in if you want to}
  100. {use this demo as application shell.}
  101.  
  102. {Mouse click in window content}
  103.  
  104.     procedure DoMouse (where: Point; modifiers: Longint);
  105.     begin
  106.     end;
  107.  
  108. {Keydown.}
  109.  
  110.     procedure DoKey (theKey: Char; modifiers: Longint);
  111.     begin
  112.     end;
  113.  
  114. {DoBackground: repeating tasks - this is called repeatedly, after every event we get.}
  115.  
  116. {Note: If you are making a really Mac-friendly program, this is where you should drive}
  117. {the animation. However, it is hard to get high framerate then, since other programs}
  118. {(the Finder included) will process events which will make it less smooth.}
  119.  
  120.     procedure DoBackground;
  121.         const
  122.             kWallBounce = 7;                            {1/10-ths of speed kept after wallbounce}
  123.             kBallDiameterSquared = 32 * 32;            {Diameter 32, squared}
  124.         var
  125.             tmpRect: Rect;
  126.             i, j: integer;
  127.             vector: Point;
  128.             saveGD: GDHandle;
  129.             savePort: GrafPtr;
  130.             tmpSpeed: Point;
  131.             squaredLength: Longint;
  132.             p1, p2, n1, n2: Point;
  133.  
  134. {Split a vector (v1) into one component parallell to another vector (direction) and one}
  135. {orthogonal to it. This is used to produce realistic bounces.}
  136.         procedure SplitVector (v1, direction: Point; var parallell, normal: Point);
  137.             var
  138.                 l2, v1pr: Longint;
  139.         begin
  140. {parallell := direction * (v1 DOT direction) /|direction|**2}
  141. {normal := v1 - parallell}
  142.  
  143.             l2 := direction.h * direction.h + direction.v * direction.v; {Squared length of "direction"}
  144.             v1pr := v1.h * direction.h + v1.v * direction.v; {Scalar product}
  145.  
  146.             parallell.h := direction.h * v1pr div l2;
  147.             parallell.v := direction.v * v1pr div l2;
  148.             normal.h := v1.h - parallell.h;
  149.             normal.v := v1.v - parallell.v;
  150.         end;
  151.  
  152. {A rather boring subroutine that moves the sprites i and j away from each other.}
  153. {I almost didn't want to put this in, making the demo unnecessarily big, but I wanted}
  154. {decent collisions. If anyone can suggest a better (simpler) collision handling for circular}
  155. {objects, I'd be happy to put it in.}
  156. {I use a line drawing algorithm for it. I'm sure there are better ways. This is of questionable}
  157. {value as reuseable code - depends on your application.}
  158.         procedure Separate (i, j: integer);
  159.             var
  160.                 initVector, nowVector: Point;
  161.                 absH, absV: integer;
  162.                 moveH, moveV: integer;
  163.                 frac: integer;
  164. {Normal signum function (which I don't think is in the libs)}
  165.             function Sgn (x: integer): integer;
  166.             begin
  167.                 if x > 0 then
  168.                     Sgn := 1
  169.                 else if x < 0 then
  170.                     Sgn := -1
  171.                 else
  172.                     Sgn := 0;
  173.             end;
  174.  
  175.         begin {Separate}
  176.             frac := 0;
  177.             initVector.h := position[i].h - position[j].h;
  178.             initVector.v := position[i].v - position[j].v;
  179.             absH := abs(initVector.h);
  180.             absV := abs(initVector.v);
  181.             moveH := Sgn(initVector.h);
  182.             moveV := Sgn(initVector.v);
  183.             if moveH = 0 then
  184.                 if moveV = 0 then
  185.                     moveV := 1;
  186.             repeat
  187.                 if absH > absV then
  188.                     begin
  189.                         position[i].h := position[i].h + moveH;
  190.                         position[j].h := position[j].h - moveH;
  191.                         frac := frac + absV;
  192.                         if frac > absH then
  193.                             begin
  194.                                 position[i].v := position[i].v + moveV;
  195.                                 position[j].v := position[j].v - moveV;
  196.                                 frac := frac - absH;
  197.                             end
  198.                     end
  199.                 else
  200.                     begin
  201.                         position[i].v := position[i].v + moveV;
  202.                         position[j].v := position[j].v - moveV;
  203.                         frac := frac + absH;
  204.                         if frac > absV then
  205.                             begin
  206.                                 position[i].h := position[i].h + moveH;
  207.                                 position[j].h := position[j].h - moveH;
  208.                                 frac := frac - absV;
  209.                             end
  210.                     end;
  211.  
  212.                 nowVector.h := position[i].h - position[j].h;
  213.                 nowVector.v := position[i].v - position[j].v;
  214.             until longint(nowVector.h) * nowVector.h + longint(nowVector.v) * nowVector.v > kBallDiameterSquared;
  215.             fixedPos[i].h := BSL(position[i].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  216.             fixedPos[i].v := BSL(position[i].v, 4);
  217.             fixedPos[j].h := BSL(position[j].h, 4); {Make fixedPos by shifting in the 4 binary "decimals"}
  218.             fixedPos[j].v := BSL(position[j].v, 4);
  219.         end;
  220.  
  221.     begin {DoBackground}
  222.         OTGetGWorld(savePort, saveGD);                {Save the port/device}
  223. {1: Erase all sprites from offScreen}
  224. {Note: We keep the rectangles r[i] for erasing on the screen, later.}
  225.         OTSetGWorld(offScreen, nil);
  226.         for i := 1 to kSpriteNumber do
  227.             begin
  228.                 r[i] := gCicn^.portRect;
  229.                 OffsetRect(r[i], position[i].h, position[i].v);
  230.                 CopyBits(backScreen^.portBits, offScreen^.portBits, r[i], r[i], srcCopy, nil);
  231.             end;
  232. {2: Change the position and speed}
  233.         for i := 1 to kSpriteNumber do
  234.             begin
  235.  {Modify fixed-point position by speed}
  236.                 fixedPos[i].h := fixedPos[i].h + speed[i].h;
  237.                 fixedPos[i].v := fixedPos[i].v + speed[i].v;
  238.  
  239. {Make position by shifting away the 4 binary "decimals"}
  240.                 if fixedPos[i].h >= 0 then
  241.                     position[i].h := BSR(fixedPos[i].h, 4)
  242.                 else
  243.                     position[i].h := BitOr(BSR(fixedPos[i].h, 4), $f000);
  244.                 if fixedPos[i].v >= 0 then
  245.                     position[i].v := BSR(fixedPos[i].v, 4)
  246.                 else
  247.                     position[i].v := BitOr(BSR(fixedPos[i].v, 4), $f000);
  248.  
  249.                 if fixedPos[i].h + speed[i].h < 0 then
  250.                     speed[i].h := abs(speed[i].h) * kWallBounce div 10 + 1;
  251.                 if fixedPos[i].v + speed[i].v < 0 then
  252.                     speed[i].v := abs(speed[i].v) * kWallBounce div 10 + 1;
  253.                 if position[i].h + gCicn^.portRect.right > offScreen^.portRect.right then
  254.                     speed[i].h := -abs(speed[i].h) * kWallBounce div 10 - 1;
  255.                 if position[i].v + gCicn^.portRect.bottom > offScreen^.portRect.bottom then
  256.                     speed[i].v := -abs(speed[i].v) * kWallBounce div 10 - 1;
  257.  
  258. {Are we in the bowl? If we are, accelerate towards the center.}
  259.                 vector.h := position[i].h + 16 - BSR(offScreen^.portRect.right, 1);
  260.                 vector.v := position[i].v + 16 - BSR(offScreen^.portRect.bottom, 1);
  261.                 if (vector.h * vector.h + vector.v * vector.v) < gBowlSize then
  262.                     begin
  263.                         speed[i].h := speed[i].h - vector.h div 2;
  264.                         speed[i].v := speed[i].v - vector.v div 2;
  265.                     end;
  266.             end; {position/speed loop}
  267.  
  268. {Check for collisions}
  269.         if gCollisionFlag then
  270.             for i := 1 to kSpriteNumber - 1 do {For all objects except the last}
  271.                 for j := i + 1 to kSpriteNumber do {compare its position to all following objects}
  272.                     begin
  273. {Find the vector between them}
  274.                         vector.h := position[i].h - position[j].h;
  275.                         vector.v := position[i].v - position[j].v;
  276.                         squaredLength := longint(vector.h) * vector.h + longint(vector.v) * vector.v;
  277. {If it is shorter than the diameter of a ball…}
  278.                         if squaredLength < kBallDiameterSquared then
  279.                             begin
  280. {Move them away from each other}
  281.                                 Separate(i, j);
  282. {Swap the speed components that are parallell to "vector" (this allows for "touches", very}
  283. {nice and realistic bounces)}
  284.                                 SplitVector(speed[i], vector, p1, n1);
  285.                                 SplitVector(speed[j], vector, p2, n2);
  286.  
  287.                                 speed[j].h := p1.h + n2.h;
  288.                                 speed[j].v := p1.v + n2.v;
  289.  
  290.                                 speed[i].h := p2.h + n1.h;
  291.                                 speed[i].v := p2.v + n1.v;
  292.  
  293. {Old Offscren Toys just swapped the speed, as commented out below. This is not as realistic.}
  294. {tmpSpeed := speed[i];}
  295. {speed[i] := speed[j];}
  296. {speed[j] := tmpSpeed;}
  297.  
  298. {Play a sound. This is not a sound demo, so I removed the sound.}
  299.                             end;
  300.                     end; {collision loop}
  301.  
  302. {3: Draw sprites in offScreen}
  303. {Note: PlotCIcon (OTPlotCicn) is not very fast! We can speed it up my pre-drawing them in some}
  304. {offscreen, and CopyBits them from there. THAT IS DONE IN THIS VERSION!!!}
  305.         for i := 1 to kSpriteNumber do
  306.             begin
  307.                 tmpRect := gCicn^.portRect;
  308.                 OffsetRect(tmpRect, position[i].h, position[i].v);
  309.                 OTPlotBoostCicn(gCicn, offScreen, tmpRect.topLeft);
  310. {FrameOval(tmpRect); {SÅ LÄNGE…}
  311.             end;
  312.  
  313. {4: Copy sprites to the screen (gWind) - both old and new position!}
  314. {Note: Depending on what limitations we have on movement, we may be able to avoid the multiple}
  315. {CopyBitsing here. E.g. if sprites always move a maximum of 2 pixels, we can copy a 2 pixels}
  316. {larger area, etc.}
  317.         OTSetGWorld(gWind, saveGD); {Vilken GD???}
  318.         for i := 1 to kSpriteNumber do
  319.             begin
  320.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  321.                 r[i] := gCicn^.portRect;
  322.                 OffsetRect(r[i], position[i].h, position[i].v);
  323.                 CopyBits(offScreen^.portBits, gWind^.portBits, r[i], r[i], srcCopy, nil);
  324.             end;
  325.         OTSetGWorld(savePort, saveGD);
  326.     end; {DoBackground}
  327.  
  328. {DoUpdate: handle update events, in this case by copying offScreen to the screen (gWind).}
  329.  
  330. {Note to beginners: A program without update events processing is not a real Mac program!}
  331. {All drawing you do must reach the update event handler in some way, or it might be lost,}
  332. {or worse, partially erased, which is really ugly.}
  333.  
  334.     procedure DoUpdate;
  335.         var
  336.             saveGD: GDHandle;
  337.             savePort: GrafPtr;
  338.     begin
  339.         OTGetGWorld(savePort, saveGD);
  340.         SetPort(gWind); {or OTSetGWorld(gWind, GetMainDevice), in case I forget to et back the device?}
  341.         BeginUpdate(gWind);
  342. {Do drawing here - in this case a CopyBits}
  343.         CopyBits(offScreen^.portBits, gwind^.portBits, gwind^.portRect, gwind^.portRect, srcCopy, nil);
  344.         EndUpdate(gWind);
  345.         OTSetGWorld(savePort, saveGD);
  346.     end;
  347.  
  348. {DoAppleMenu and DoFileMenu: handle menu selections}
  349.  
  350.     procedure DoAppleMenu (item: integer);
  351.         var
  352.             str: Str255;
  353.             h: Handle;
  354.             saveGD: GDHandle;
  355.             savePort: GrafPtr;
  356.             ignore: integer;
  357.     begin
  358.         if item = 1 then
  359.             begin
  360.                 if Alert(kAboutAlertID, nil) = 1 then
  361.                     ; {Ignore result}
  362.             end
  363.         else
  364. {Apple menu other than "About": Code from TransSkel}
  365.             begin
  366.                 OTGetGWorld(savePort, saveGD); {I guess GetPort would be ok}
  367.                 GetItem(appleMenu, item, str);
  368.                 SetResLoad(false);
  369.                 h := GetNamedResource('DRVR', str);
  370.                 SetResLoad(true);
  371.                 if h <> nil then
  372.                     begin
  373.                         ResrvMem(SizeResource(h) + $1000);
  374.                         ignore := OpenDeskAcc(str);
  375.                     end;
  376.                 OTSetGWorld(savePort, saveGD);
  377.             end;
  378.     end; {DoAppleMenu}
  379.  
  380.     procedure DoFileMenu (item: integer);
  381.         var
  382.             start, finish, frames: Longint;
  383.     begin
  384.         case item of
  385.             1:
  386. {Run animation without event processing until the user clicks the mouse}
  387. {Note: This runs the animation at maximum speed. In real programs, we}
  388. {must limit the speed with the system clock, e.g. inspect TickCount.}
  389.                 begin
  390.                     start := TickCount;
  391.                     frames := 0;
  392.                     while not Button do
  393.                         begin
  394.                             DoBackground;
  395.                             frames := frames + 1;
  396.                         end;
  397.                     finish := TickCount;
  398.                     ParamText(stringof(frames * 60 div (finish - start), ' frames/second'), '', '', '');
  399.                     if Alert(129, nil) = 1 then
  400.                         ;
  401.                 end;
  402.  
  403. {To run the animation only, without measuring frames/sec, you just have to d this:}
  404. {while not Button do}
  405. {DoBackground;}
  406.             2: 
  407.                 begin
  408.                     gCollisionFlag := not gCollisionFlag;
  409.                     CheckItem(fileMenu, 2, gCollisionFlag);
  410.                 end;
  411. {Set the flag that tells the program to quit.}
  412.             4: 
  413.                 gWhoa := true;
  414.         end; {case}
  415.     end; {DoFileMenu}
  416.  
  417. { --- PART 4: Event processing: -----------------------------------------}
  418.  
  419. {MenuSelection: Menu selection by mouse or command-key:}
  420.  
  421.     procedure MenuSelection (whatSelection: longInt);
  422.     begin
  423.         case HiWord(whatSelection) of
  424.             kAppleID: 
  425.                 DoAppleMenu(LoWord(whatSelection));
  426.             kFileID: 
  427.                 DoFileMenu(LoWord(whatSelection));
  428.         end; {case}
  429.         HiLiteMenu(0);
  430.     end;
  431.  
  432. {MainLoop: get and process events. This is the boring standard part of all programs. I prefer}
  433. {using TransSkel to get rid of it. I don't here since I want this code to be stand-alone.}
  434.  
  435.     procedure MainLoop;
  436.         const
  437.             kSleep = 5; {Real programs may modify the sleep time depending on whether or not they are in the front}
  438.         var
  439.             hasEvent: Boolean;
  440.             theEvent: EventRecord;
  441.             theKey: Char;
  442.             whatSelection: Longint;
  443.             whichPart: integer;
  444.             whichWindow: WindowPtr;
  445.             r: rect;
  446.     begin
  447. {Get the next event. Use WaitNextEvent if possible.}
  448.         if gHasWNE then
  449.             hasEvent := WaitNextEvent(everyEvent, theEvent, kSleep, nil)
  450.         else
  451.             begin
  452.                 SystemTask;
  453.                 hasEvent := GetNextEvent(everyEvent, theEvent);
  454.             end;
  455.  
  456. {OK, so what happened then?}
  457.         if hasEvent then
  458.             case theEvent.what of
  459.                 mouseDown: 
  460.                     begin
  461.                         whichPart := FindWindow(theEvent.where, whichWindow);
  462.                         case whichPart of
  463.                             inMenuBar: 
  464.                                 begin
  465.                                     whatSelection := MenuSelect(theEvent.where);
  466.                                     MenuSelection(whatSelection);
  467.                                 end;
  468.                             inSysWindow: 
  469.                                 SystemClick(theEvent, whichWindow);
  470.                             inGoAway: 
  471.                                 if (TrackGoAway(whichWindow, theEvent.where)) then
  472.                                     gWhoa := true;
  473.                             inDrag: 
  474.                                 begin
  475.                                     if (whichWindow <> FrontWindow) and (BitAnd(theEvent.modifiers, cmdKey) = 0) then
  476.                                         SelectWindow(whichWindow);
  477. {$IFC UNDEFINED THINK_PASCAL}
  478.                                     r := qd.screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  479. {$ELSEC}
  480.                                     r := screenBits.bounds;            {How big is the screen? (Note: Don't use screenBits for other things: it isn't a valid BitMap any more!)}
  481. {$ENDC}
  482.                                     r.top := r.top + kMBarHeight;        { Skip down past menu bar    }
  483.                                     InsetRect(r, 4, 4);
  484.                                     DragWindow(whichWindow, theEvent.where, r);
  485.                                 end;
  486.                             inGrow: 
  487.                                 ;  {Ignored - we don't resize}
  488.                             inContent: 
  489.                                 if (whichWindow <> FrontWindow) then
  490.                                     SelectWindow(whichWindow)
  491.                                 else
  492.                                     DoMouse(theEvent.where, theEvent.modifiers); {Go to application-specific mouse down handling}
  493.                         end; {case whichPart}
  494.                     end; {mouseDown}
  495.                 keyDown, autoKey: 
  496.                     begin
  497.                         theKey := char(BitAnd(theEvent.message, charCodeMask));
  498.                         if (BitAnd(theEvent.modifiers, cmdKey) <> 0) then
  499.                             MenuSelection(MenuKey(theKey))
  500.                         else
  501.                             DoKey(theKey, theEvent.modifiers);
  502.                     end;
  503.                 updateEvt:
  504. {There's only one window to bother with here, but let's make sure that's the one the Mac wants to update.}
  505.                     if WindowPtr(theEvent.message) = gWind then
  506.                         DoUpdate;
  507. {Handle disk inserts like TransSkel.}
  508.                 diskEvt: 
  509.                     if (HiWord(theEvent.message) <> noErr) then
  510.                         begin
  511.                             DILoad;
  512.                             if DIBadMount(Point($00400040), theEvent.message) = 0 then
  513.                                 ;
  514.                             DIUnload;
  515.                         end; {diskEvt}
  516.                 otherwise {Other events are ignored}
  517.             end; {case}
  518.  
  519.         DoBackground;
  520.     end;
  521.  
  522. { --- PART 5: Initializations: -----------------------------------------}
  523.  
  524. {OTInit: Initialize global flags, menus and window}
  525.  
  526.     procedure OTInit;
  527.         const
  528. {Trap numbers}
  529.             _WaitNextEvent = $A860;
  530.             _GetCIcon = $AA1E; {E.g. any Color QuickDraw routine}
  531.             k32bQD = $AB1D;
  532.             _SndPlay = $A805;
  533.     begin
  534.  
  535. {In case this isn't Think Pascal we have to make the standard inits ourselves.}
  536. {$IFC UNDEFINED THINK_PASCAL}
  537.         InitGraf(@qd.thePort);
  538.         InitFonts;
  539.         InitWindows;
  540.         InitMenus;
  541.         TEInit;
  542.         InitDialogs(nil);
  543. {InitCursor;}
  544.         MaxApplZone;
  545. {$ENDC}
  546.  
  547.         OTInitGlobals;                {Initialize the OffscreenToysUtils unit}
  548.  
  549.         gWhoa := false;
  550.         gCollisionFlag := false;
  551.  
  552. {We could check with Gestalt instead, but that isn't necessary here since we aren't using any}
  553. {optional services (like QuickTime).}
  554.  
  555. {Get the window, a color window if we are going to use color.}
  556.         if gColorQDFlag then
  557.             gWind := GetNewCWindow(kWindId, nil, WindowPtr(-1))
  558.         else
  559.             gWind := GetNewWindow(kWindId, nil, WindowPtr(-1));
  560.  
  561. {Some menus. We could read these from resources.}
  562.         appleMenu := NewMenu(kAppleID, stringof(char($14)));
  563.         AppendMenu(appleMenu, 'About OffscreenToys…;(-');
  564.         AddResMenu(appleMenu, 'DRVR');
  565.         InsertMenu(appleMenu, 0);            { put apple menu at end of menu bar }
  566.         fileMenu := NewMenu(kFileID, 'File');
  567.         AppendMenu(fileMenu, 'Try max speed;Collisions;(-;Quit/Q');
  568.         InsertMenu(fileMenu, 0);            { put file menu at end of menu bar }
  569.         DrawMenuBar;
  570.     end;
  571.  
  572. {OTOffscreensInit: Initialize offscreen grafports (worlds) and draw in them.}
  573.  
  574.     procedure OTOffscreensInit;
  575.         var
  576.             saveGD: GDHandle;
  577.             savePort: GrafPtr;
  578.             thePat: PixPatHandle;
  579.             r: Rect;
  580.             i: integer;
  581.             colorFlag: Boolean;
  582.         const
  583.             patID = 128;
  584.  
  585. {A little routine for setting the forecolor with a single line.}
  586.         procedure OTForeColor (red, green, blue: integer);
  587.             var
  588.                 theColor: RGBColor;
  589.         begin
  590.             theColor.red := red;
  591.             theColor.green := green;
  592.             theColor.blue := blue;
  593.             RGBForeColor(theColor);
  594.         end;
  595.  
  596.     begin {OTOffscreensInit}
  597.         OTGetGWorld(savePort, saveGD);
  598.  
  599.         OTNewGWorld(offScreen, gWind^.portRect);
  600.         OTNewGWorld(backScreen, gWind^.portRect);
  601.  
  602.         OTSetGWorld(backScreen, nil);
  603.  
  604. {Do some drawing in backScreen. First, we paint a pattern (using a 'ppat' resource):}
  605.  
  606. {For drawing the background, let's make a local flag that tells us if we shold draw}
  607. {b/w patterns or color ones.}
  608.         if gColorQDFlag then
  609.             colorFlag := (CGrafPtr(backScreen)^.portPixMap^^.pixelSize > 1)
  610.         else
  611.             colorFlag := false;
  612.  
  613.         if colorFlag then
  614.             begin
  615.                 thePat := GetPixPat(patID);
  616.                 PenPixPat(thePat)
  617.             end
  618.         else
  619.             begin
  620.                 thePat := PixPatHandle(GetResource('ppat', patID));
  621.                 PenPat(thePat^^.pat1Data);
  622.             end;
  623.         PaintRect(backScreen^.portRect);
  624.         PenNormal;
  625.  
  626. {Then we draw some circles.}
  627.  
  628.         r := backScreen^.portRect;
  629.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  630.         gBowlSize := longint(r.right - r.left) * (r.right - r.left) div 4;        {Tells how big the "bowl" is!}
  631.         if colorFlag then
  632.             begin
  633.                 OTForeColor(-10000, -10000, -10000);
  634.                 PaintOval(r);
  635.             end
  636.         else
  637. {$IFC UNDEFINED THINK_PASCAL}
  638.             FillOval(r, qd.ltGray);
  639. {$ELSEC}
  640.         FillOval(r, ltGray);
  641. {$ENDC}
  642.  
  643.         InsetRect(r, (r.right - r.left) div 8, (r.bottom - r.top) div 8);
  644.         if colorFlag then
  645.             begin
  646.                 OTForeColor(-25000, -25000, -25000);
  647.                 PaintOval(r);
  648.             end
  649.         else
  650. {$IFC UNDEFINED THINK_PASCAL}
  651.             FillOval(r, qd.gray);
  652. {$ELSEC}
  653.         FillOval(r, gray);
  654. {$ENDC}
  655.  
  656.         InsetRect(r, (r.right - r.left) div 6, (r.bottom - r.top) div 6);
  657.         if colorFlag then
  658.             begin
  659.                 OTForeColor(20000, 20000, 20000);
  660.                 PaintOval(r);
  661.             end
  662.         else
  663. {$IFC UNDEFINED THINK_PASCAL}
  664.             FillOval(r, qd.dkGray);
  665. {$ELSEC}
  666.         FillOval(r, dkGray);
  667. {$ENDC}
  668.  
  669.         InsetRect(r, (r.right - r.left) div 5, (r.bottom - r.top) div 5);
  670.         if colorFlag then
  671.             OTForeColor(0, 0, 0);
  672.         PaintOval(r);
  673.  
  674. {Done drawing!}
  675. {For your own hacks, consider using a PICT resource and use GetPicture and DrawPicture to draw the}
  676. {background. Note that you'll need both a color and a b/w picture if you want it to look good in b/w.}
  677.  
  678.         OTSetGWorld(offScreen, nil);
  679.         CopyBits(backScreen^.portBits, offScreen^.portBits, backScreen^.portRect, backScreen^.portRect, srcCopy, nil);
  680.  
  681.         OTSetGWorld(savePort, saveGD);
  682.  
  683. {Get the cicn resource}
  684. {Note: You can, of course, use several cicns and switch between.}
  685.         gCicn := OTGetBoostCicn(128);
  686.  
  687. {Initialize the sprite information arrays:}
  688.  
  689.         for i := 1 to kSpriteNumber do
  690.             begin
  691.                 position[i].h := Rand(offScreen^.portRect.right - 32);
  692.                 position[i].v := (i - 1) * (offScreen^.portRect.bottom - 32) div 5 + Rand((offScreen^.portRect.bottom - 32) div 5);
  693.                 fixedPos[i].h := BSL(position[i].h, 4);
  694.                 fixedPos[i].v := BSL(position[i].v, 4);
  695.                 speed[i].h := Random mod 32;
  696.                 speed[i].v := Random mod 32;
  697.             end;
  698.     end;
  699.  
  700. { --- MAIN PROGRAM BODY: -----------------------------------------}
  701.  
  702. begin
  703.     OTInit;                    {General initializations}
  704.     OTOffscreensInit;        {Set up the offscreen grafports}
  705.     InitCursor;                {Set the cursor to arrow in case it isn't.}
  706.  
  707. {Run until quit or click in the close box.}
  708.     repeat
  709.         MainLoop;
  710.     until gWhoa;
  711.  
  712. {No cleanup is necessary here.}
  713. {We could DisposeGWorld, but that isn't necessary when we are quitting.}
  714. end.